package com.nulldev.util.web.nTubeExtensions.ntube_proxy;

import java.io.InputStream;
import java.net.Proxy;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.concurrency.threadIt.v4.emapi.cntft.ControllableFutureTask;
import com.nulldev.util.internal.backport.concurrency9.Lists;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.manage.experiments.Experiments;
import com.nulldev.util.web.HttpClient.HttpClient;
import com.nulldev.util.web.HttpClient.HttpClientFlags;
import com.nulldev.util.web.HttpClient.HttpMethod;
import com.nulldev.util.web.HttpClient.HttpRequest;
import com.nulldev.util.web.HttpClient.HttpResponse;
import com.nulldev.util.web.HttpClient.Version;
import com.nulldev.util.web.HttpServer3.ByteReadRange;
import com.nulldev.util.web.HttpServer3.Response;
import com.nulldev.util.web.HttpServer3.pages.v3.HttpPage3.Request;
import com.nulldev.util.web.URLUtils.Entry;

public class ProxyDoFunction {

	public static final boolean USE_COPYABLE = Experiments.get("ntube://proxy-use-copyable/").isActive() || IOUtils.canUseDMA();

	public final static Logger log = LoggerFactory.getLoggerD(Arguments.hasArgument("--ProxyAPI.debug"), true);

	/** Fixed timeout for the proxy, in seconds. */
	private static final int TIMEOUT_WAIT = 34;

	private static final boolean ENABLE_MODERN_TRANSPORT = Experiments.get("ntube://proxy-use-modern-transport/").isActive();
	private static final boolean USE_OK_CHUNKING = Experiments.get("ntube://okhttp-proxy-chunking/").isActive();
	private static final boolean ALLOW_YT_POST = Experiments.get("ntube://proxy-use-post/").isActive();

	private static final byte[] YT_POST_PAYLOAD = new byte[]
		{ 'x', '\u0000' };

	@SuppressWarnings("resource")
	public static boolean doRegularProxy(final List<Entry> entries, final ControllableFutureTask<Response> r, final String furl, final Request request,
			final boolean forceLegacy, final boolean youtubeVideo, final boolean youtubeHLS, final boolean privacy_noRequestCountTracking, final Proxy proxy,
			final int bufferSize, final boolean youtubeLiveVideo, final int tryCount, final ByteReadRange[] byteRanges, final String wk_force_clen)
			throws Exception {
		final HttpRequest<InputStream> _x = (forceLegacy && !ENABLE_MODERN_TRANSPORT ? HttpClient.getStreamLegacy() : HttpClient.getStream())
				/* */
				.url(furl)
				/* */
				.timeout(Duration.ofSeconds(TIMEOUT_WAIT))
				/* */
				.headers(entries)
				/* */
				.enableBuffering(bufferSize)
				/* */
				.proxy(proxy)
				/* */
				.tryFeatureFlag(HttpClientFlags.FEATURE_FLAG_OK_FORCE_CHUNKING, USE_OK_CHUNKING)
				/* */
				.tryFeatureFlag(HttpClientFlags.FEATURE_FLAG_BACKPORT_ENABLE_INPUT_STREAM_MONITOR, youtubeVideo)
				/* */
				.preferVersion(tryCount >= 2 ? Version.HTTP_1_1 : null);
		final HttpResponse<InputStream> x;
		if (youtubeVideo && ALLOW_YT_POST && tryCount < 2) {
			x = _x.header("Accept", "*/*").header("Content-Length", "2").create(HttpMethod.POST, YT_POST_PAYLOAD);
		} else {
			x = _x.create(HttpMethod.as(request.getType().name()), request.getInputStream());
		}

		final int code = x.status();
		/* HTTP/3 workaround */
		if (code == 302) {
			final String location = x.getHeaderField("location");
			log.debug("Redirecting to: " + location);
			return doRegularProxy(entries, r, location, request, forceLegacy, youtubeVideo, youtubeHLS, privacy_noRequestCountTracking, proxy, bufferSize,
					youtubeLiveVideo, tryCount, byteRanges, wk_force_clen);
		}

		log.debug("(RP) " + code + "\t" + furl);
		final boolean hasEncoding = (x.headers().containsKey("content-encoding") || x.headers().containsKey("Content-Encoding"))
				/*
				 * YouTube now lies even more about compression, this is probably made worse by
				 * compression >:(
				 */
				&& !furl.contains("www.youtube.com/live_chat");
		String ce = null;
		final boolean copyableAllowed = USE_COPYABLE && !furl.contains("www.youtube.com/api/timedtext") && !furl.contains("/slices%3D");
		try {
			if (hasEncoding) {
				if (x.headers().containsKey("Content-Encoding")) {
					ce = x.headers().get("Content-Encoding").get(0);
				} else {
					ce = x.headers().get("content-encoding").get(0);
				}
			}
		} catch (Exception e) {
			log.error(e);
		}

		final List<Entry> headers = new ArrayList<Entry>(x.headers().size());
		for (final Map.Entry<String, List<String>> entry : x.headers().entrySet()) {
			if (BLACKLISTED_HEADERS.contains(entry.getKey()))
				continue;
			final String value = ArrayUtils.join(entry.getValue(), ";");
			headers.add(new Entry(entry.getKey(), value));
		}

		String l = "*";
		final long _ll = x.length();
		if (_ll >= 0 && !youtubeVideo) {
			l = String.valueOf(_ll);
		}

		if (!x.headers().containsKey("content-range") && request.hasHeader("Range")) {
			final String req_range = request.getHeaders().get("Range");
			final String contentRange;
			final String overridenLength = wk_force_clen != null ? wk_force_clen : l;
			if (!req_range.endsWith("-")) {
				final String xv = "bytes=";
				contentRange = "bytes " + req_range.substring(req_range.indexOf(xv) + xv.length()) + "/" + overridenLength;
			} else if (req_range.endsWith("-")) {
				final String xv = "bytes=";
				contentRange = "bytes " + req_range.substring(req_range.indexOf(xv) + xv.length()) + "/" + overridenLength;
			} else if (byteRanges != null && byteRanges.length == 1) {
				contentRange = "bytes " + byteRanges[0].format() + "/" + overridenLength;
			} else {
				contentRange = null;
			}
			headers.add(new Entry("Content-Range", contentRange));
		}

		if (copyableAllowed) {
			r.complete(new Response(code, x, x).header("NT-ProxyAPI-Handler", "PROXY_DO").headers(headers)
					.deferCompression((!hasEncoding || ce == null) && !youtubeHLS).setNativeEncoding(hasEncoding, ce)
					.requestKeepAliveDisable(youtubeLiveVideo));
			return true;
		} else {
			r.complete(new Response(code, x.data(hasEncoding)).header("NT-ProxyAPI-Handler", "PROXY_DO").headers(headers)
					.deferCompression((!hasEncoding || ce == null) && !youtubeHLS).setNativeEncoding(hasEncoding, ce)
					.requestKeepAliveDisable(youtubeLiveVideo));
			return true;
		}
	}

	private static final List<String> BLACKLISTED_HEADERS = Lists.of("alt-svc", "client-protocol", "cache-control", "cross-origin-resource-policy",
			"connection", "server", "server-name", "set-cookie", "set-cookie2", "access-control-allow-origin", "access-control-allow-credentials",
			"access-control-expose-headers", "access-control-max-age", "access-control-allow-methods", "access-control-allow-headers", "location", "upgrade",
			"etag", "x-restrict-formats-hint", "keep-alive", "nt-proxyapi-handler", "transfer-encoding");
}
